function cl( msg){ 
	$( '#logArea').html( $( '#logArea').html() + msg + '<br />' );
}
(function( $){
function Cache(){
	this.cache = {};
}
Cache.prototype.set = function( key, value, isObject){
	if( isObject ){
		if( window.JSON && JSON.stringify){
			this.cache[ key ] = JSON.stringify( value);
		}else{
			throw new Error( 'don\'t support JSON.stringify function');
		}
	}else{
		this.cache[ key ] = value;
	}
};
Cache.prototype.get = function( key, isObject ){
	if( window.JSON && JSON.parse ){
		return this.cache[ key] ? 
			( isObject ?  JSON.parse( this.cache[ key]) : this.cache[ key] ) :
			null;
	}else{
		return this.cache[ key] ? 
			( isObject ?  ( new Function( 'return ' + this.cache[ key] ) )() : this.cache[ key] ) :
			null;
	}
};
Cache.prototype.has = function( key){
	return this.cache.hasOwnProperty( key ) ? true : false;
}
Cache.prototype.remove = function( keys){
	for( var key in keys){
		if( this.cache.hasOwnProperty( key) ){
			delete this.cache[ key]
		}
	}
};

var KEY = {};
KEY.carriage    = 13;
KEY.upArrow     = 38;
KEY.downArrow   = 40;
KEY.leftArrow	= 37;
KEY.rightArrow	= 39;
KEY.esc  	    = 27;

/**
 * options
 * ajax_url:		string	 required
 * min_len:			integer	 optional -	when to trigger ajax request
 * latency:			integer	 optional - waiting time
 * class_name:		string	 optional
 * loadding:		string	 optional
 * no_result:		string	 optional
 */
$.fn.autocomplete = function( options){
if( ! options.ajax_url ){
	return;
}

var MIN_LEN		= options.min_len || 3;
var LATENCY		= options.latency || 200;
var LOADING		= options.loading || 'loading...';
var NO_RESULT	= options.no_result || 'no results';
var CLASS_NAME	= options.class_name || 'ac';

var timer		= null; // flag for ajax
var timer2		= null; // flat for no_result
var cache		= new Cache;
var index		= -1; // hightlight index
var ele_count	= 0; // count of autocomplete result; ie. count of li
var last_keywords = '';

var div_ele = $( '<div>');
var ul_ele = $( '<ul>');
var input_ele = this;

div_ele.addClass( CLASS_NAME );
div_ele.css( {
	top: this.offset().top + this.outerHeight(),
	left: this.offset().left,
	width: this.width() + 4
});
div_ele.append( ul_ele);
$( document.body).append( div_ele);

var isEnoughChars = function(){
	var len = $.trim( input_ele.val() ).length;
	if( len < MIN_LEN){
		return false;
	}else{
		return true;
	}
};
var render = function( k ){
	index = -1; // rewind highlight index
	var json = cache.get( k, true);
	var q = json[ 0];
	var lists = json[ 1];
	var i = 0, len = lists.length;	
	if( len < 1){ // no results
		renderHint( NO_RESULT);
		if( timer2 ){
			clearTimeout( timer2);
			timer2 = null;
		}
		timer2 = setTimeout( function(){
			ul_ele.html( '');
			hideList();
		}, 2000);
		return;
	}
	if( timer2){
		clearTimeout( timer2);
		timer2 = null;
	}
	var li;
	ul_ele.html( '');
	showList();
	for(; i < len; i++){
		li = $( '<li>');
		ul_ele.append( li);
		li.attr( '_name', i);
		li.attr( '_value', lists[ i]);
		li.html( lists[ i].replace( q, '<b>' + q + '</b>') );
		ul_ele.append( li);
	}
	ele_count = len;
};
var renderList = function(){
	var now_keywords = $.trim( input_ele.val() );
	var lists;
	if( last_keywords == now_keywords ){
		return;
	}
	last_keywords = now_keywords;
	if( cache.has( now_keywords ) ){
		render( now_keywords);
	}else{ // ajax to get list
		renderHint( LOADING);
		if( timer){
			clearTimeout( timer);
			timer = null;
		}
		timer = setTimeout( function(){
			$.get( options.ajax_url + '?q=' + encodeURIComponent( now_keywords), function( data){
				cache.set( now_keywords, data);
				render( now_keywords);
			}, 'text' );
		}, LATENCY);
	}
};
var renderHint = function( msg){
	showList();
	index = 10000; // avoid select HINT
	ul_ele.html( '');
	ele_count = 0;
	li = $( '<li>');
	li.html( msg );
	ul_ele.append( li);
};
var submitForm = function(){
	var form_ele = input_ele.parents( 'form');
	if( typeof form_ele[ 0].submit === 'function'){
		form_ele.submit();
	}else{ // in case that input[type="submit"] named "submit"
		form_ele.find( 'input:submit').click();
	}
}
var hideList = function(){
	div_ele.css( 'display', 'none');
}
var showList = function(){
	div_ele.css( 'display', 'block');
};
var setInputValue = function( v){
	input_ele.val( v);
};
var loadSavedValue = function(){
	input_ele.val( last_keywords);
};
input_ele.bind( 'keyup', function( event){
	switch( event.keyCode){
		case KEY.downArrow:
			if( ! isEnoughChars() ){
				return;
			}
			if( ele_count < 1 ){ // in case that input_ele has default value
				renderList();
				return;
			}
			showList();
			if( index !== -1){ // index == -1: begin
				ul_ele.find( 'li:eq(' + index + ')').removeClass( 'curr');
			}
			index++;
			if( index !== ele_count ){
				ul_ele.find( 'li:eq(' + index + ')').addClass( 'curr');
				setInputValue( ul_ele.find( 'li:eq(' + index + ')').attr( '_value') );
			}else{ // 
				index = -1;
				loadSavedValue();
			}
			break;
		case KEY.upArrow:
			if( ! isEnoughChars() ){
				return
			}
			if( ele_count < 1 ){
				renderList();
				return;
			}
			showList();
			if( index !== -1 ){
				ul_ele.find( 'li:eq(' + index + ')').removeClass( 'curr');
			}
			index--;
			if( index === -2){
				index = ele_count - 1;
			}
			if( index !== -1){
				ul_ele.find( 'li:eq(' + index + ')').addClass( 'curr');
				setInputValue( ul_ele.find( 'li:eq(' + index + ')').attr( '_value'));
			}else{
				loadSavedValue();
			}
			break;
		case KEY.carriage:
			if( index !== -1){
				setInputValue( ul_ele.find( 'li:eq(' + index + ')').attr( '_value') ); 
				// REMIND: submit automatically
			}
			break;
		case KEY.esc:
		case KEY.leftArrow:
		case KEY.rightArrow:
		case 16: case 17: case 18:case 33: case 34: case 35: case 36: // keys don't send ajax
			break;	
		default:
			if( ! isEnoughChars() ){
				hideList();
				return;
			}
			renderList();
	}
});
input_ele.bind( 'keydown', function( event){
	if( event.keyCode === KEY.esc){
		hideList();
		setTimeout( function(){
			input_ele.focus(); // in case that opera lose focus
		}, 0)
		return false; // in case that ie clear the input
	}
});
ul_ele.bind( 'click', function( event){
	var srcEle = event.target || event.srcElement;		
	var keywords = srcEle.getAttribute( '_value');
	input_ele.val( keywords);	
	submitForm();
});
ul_ele.bind( 'mouseover', function( event){
	var srcEle = event.target || event.srcElement;
	if( srcEle.tagName === 'B'){
		srcEle = srcEle.parentNode;
	}
	if( index !== -1){
		ul_ele.find( 'li:eq(' + index + ')').removeClass( 'curr');
	}
	index = srcEle.getAttribute( '_name');
	ul_ele.find( 'li:eq(' + index + ')').addClass( 'curr');
});
ul_ele.bind( 'mouseout', function( event){
	var srcEle = event.target || event.srcElement;
	if( srcEle.tagName === 'B'){
		srcEle = srcEle.parentNode;
	}
	index = srcEle.getAttribute( '_name');
	if( index != 0 && index != ele_count - 1 ){
		ul_ele.find( 'li:eq(' + index + ')').removeClass( 'curr');
	}
});
input_ele.bind( 'blur', function(){
	setTimeout( function(){
		hideList();		
	}, 100);
});

input_ele.attr( 'autocomplete', 'off');

setTimeout( function(){
	input_ele.focus(); 
}, 0);
};
})(jQuery);
